/**
 *    Copyright 2009-2020 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.executor;

import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;

/**
 * @author Jeff Butlefr
 */
/*xxx: 应用系统在执行一条sql语句时，会将 sql语句 以及相关参数，通过网络 发送到 数据库系统，比较耗资源 */

  /*xxx: jdbc中的批处理，只支持 insert,update,delete等类型的sql语句，不支持select类型的sql语句*/
public class BatchExecutor extends BaseExecutor {

  public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;

  /*xxx: 缓存多个 Statemetn对象，其中每个 Statement 对象中 都缓存了 多条 sql 语句*/
  private final List<Statement> statementList = new ArrayList<>();
  /*xxx: 记录批处理的结果，BatchResult中，通过 updateCounts字段，记录每个Statement执行批处理的结果*/
  private final List<BatchResult> batchResultList = new ArrayList<>();
  /*xxx: 记录当前执行的sql语句*/
  private String currentSql;
  private MappedStatement currentStatement;

  public BatchExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  @Override
  public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
    /*xxx: 获取配置对象*/
    final Configuration configuration = ms.getConfiguration();
    /*xxx: 创建 StatementHandler对象*/
    final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
    final BoundSql boundSql = handler.getBoundSql();
    /*xxx: 获取sql语句*/
    final String sql = boundSql.getSql();
    final Statement stmt;
    /*xxx: 如果当前执行的 sql 模式 与 上次执行的 sql 模式相同，且对应的MappedStatement对象相同*/
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      /*xxx: 获取 statementList 集合中，最后一个 Statement对象*/
      int last = statementList.size() - 1;
      stmt = statementList.get(last);
      applyTransactionTimeout(stmt);
      /*xxx: 绑定实参，处理 "？" 占位符*/
      handler.parameterize(stmt);// fix Issues 322
      /*xxx: 查找对应的BatchResult对象，并记录用户传入的实参*/
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
    } else {
      Connection connection = getConnection(ms.getStatementLog());
      /*xxx: 创建新的 Statement对象*/
      stmt = handler.prepare(connection, transaction.getTimeout());
      /*xxx: 绑定实参，处理"?"占位符*/
      handler.parameterize(stmt);    // fix Issues 322
      /*xxx: 更新 currentSql 和 currentStatement*/
      currentSql = sql;
      currentStatement = ms;
      /*xxx: 将新创建的 Statement 对象 ，添加到 statementList 集合中*/
      statementList.add(stmt);
      /*xxx: 添加新的 BatchResult对象*/
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
    /*xxx: 底层通过调用 Statement.addBatch 方法添加 SQL语句*/
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException {
    Statement stmt = null;
    try {
      flushStatements();
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

  @Override
  protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
    flushStatements();
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    Connection connection = getConnection(ms.getStatementLog());
    Statement stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    Cursor<E> cursor = handler.queryCursor(stmt);
    stmt.closeOnCompletion();
    return cursor;
  }

  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    try {
      /*xxx: 用于储存批处理的结果*/
      List<BatchResult> results = new ArrayList<>();
      /*xxx: 明确制定了要回滚事务，则直接返回空集合,忽略 statementList 集合中记录的 sql语句*/
      if (isRollback) {
        return Collections.emptyList();
      }
        /*xxx: 遍历 statementList 集合*/
      for (int i = 0, n = statementList.size(); i < n; i++) {
        /*xxx: 获取 Statement对象*/
        Statement stmt = statementList.get(i);
        applyTransactionTimeout(stmt);
        /*xxx: 获取对应 BatchResult对象*/
        BatchResult batchResult = batchResultList.get(i);
        try {
          /*xxx: 调用 Statement.executeBatch 方法，批量执行其中记录的 sql语句，并使用返回的
          *   int数组，更新 BatchResult.updateCoutns字段*/
          batchResult.setUpdateCounts(stmt.executeBatch());
          MappedStatement ms = batchResult.getMappedStatement();
          List<Object> parameterObjects = batchResult.getParameterObjects();
          /*xxx: 获取配置的 KeyGenerator对象*/
          KeyGenerator keyGenerator = ms.getKeyGenerator();
          if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
            Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
            /*xxx: 获取数据库生成的主键，并设置到 parameterObjects中*/
            jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
          } else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { //issue #141
            /*xxx: 对于其它类型的 KeyGenerator，会调用其 processAfter方法*/
            for (Object parameter : parameterObjects) {
              keyGenerator.processAfter(this, ms, stmt, parameter);
            }
          }
          // Close statement to close cursor #1109
          closeStatement(stmt);
        } catch (BatchUpdateException e) {
          StringBuilder message = new StringBuilder();
          message.append(batchResult.getMappedStatement().getId())
              .append(" (batch index #")
              .append(i + 1)
              .append(")")
              .append(" failed.");
          if (i > 0) {
            message.append(" ")
                .append(i)
                .append(" prior sub executor(s) completed successfully, but will be rolled back.");
          }
          throw new BatchExecutorException(message.toString(), e, results, batchResult);
        }
        /*xxx: 添加 BatchResult 到 results 集合 */
        results.add(batchResult);
      }
      return results;
    } finally {
      for (Statement stmt : statementList) {
        closeStatement(stmt);
      }
      currentSql = null;
      statementList.clear();
      batchResultList.clear();
    }
  }

}
